﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Threading;
using System.Globalization;
using System.IO;
using System.Diagnostics;

namespace System.Windows.Media.DirectShow.Filters
{
    internal class DirectShowMediaStreamSource : MediaStreamSource
    {



        private AudioRendererPin _audioRenderPin;
        private VideoRendererPin _videoRenderPin;
        private const int maxBuffer = 15;
        private Queue<IMediaSample> samples = new Queue<IMediaSample>(maxBuffer);
        private ManualResetEvent sampleEvent = new ManualResetEvent(false);
        //private ManualResetEvent openEvent = new ManualResetEvent(false);

        private MediaStreamDescription audioStreamDescription;
        private MediaStreamDescription videoStreamDescription;
        private Dictionary<MediaSampleAttributeKeys, string> emptyDict = new Dictionary<MediaSampleAttributeKeys, string>();

        private bool mediaOpened = false;
        private bool stopped = false;
        private IMediaSample lastSample;
        private MediaElement mediaElement;

        public DirectShowMediaStreamSource(MediaElement mediaElement)
        {
            this.mediaElement = mediaElement;
        }

        internal AudioRendererPin AudioRenderPin
        {
            get { return _audioRenderPin; }
            set { _audioRenderPin = value; }
        }

        internal VideoRendererPin VideoRenderPin
        {
            get { return _videoRenderPin; }
            set { _videoRenderPin = value; }
        }

        protected override void CloseMedia()
        {
            lock (this)
            {
                mediaOpened = false;
                stopped = true;
                sampleEvent.Set();
            }
        }

        protected override void GetDiagnosticAsync(MediaStreamSourceDiagnosticKind diagnosticKind)
        {
            throw new NotImplementedException();
        }

        protected override void GetSampleAsync(MediaStreamType mediaStreamType)
        {
            // Wait until new sample is available
            bool wait = true;
            while (wait)
            {
                lock (this)
                {
                    if (stopped) return;
                }

                lock (samples)
                {
                    if (samples.Count == 0)
                    {
                        sampleEvent.Reset();
                        wait = true;
                    }
                    else
                    {
                        sampleEvent.Set();
                        wait = false;
                    }
                }

                if (wait)
                    sampleEvent.WaitOne();
            }

            IMediaSample sample = null;
            try
            {
                lock (samples)
                {
                    sample = samples.Dequeue();
                }

                //Debug.WriteLine("GetSampleAsync {0}-{1} Size: {2}", sample.StartTime, sample.EndTime, sample.Length);
                //this.mediaElement.Dispatcher.BeginInvoke(() =>
                //{
                //    Debug.WriteLine(this.mediaElement.Position);
                //});

                MediaStreamSample streamSample = null;

                MemoryStream stream = null;
                // Null is end of the stream
                if (sample.Data != null)
                    stream = new MemoryStream(sample.Data);
                else if (samples.Count == 0)
                {
                    // Raise completed event
                    if (this.AudioRenderPin.Filter.Graph is FilterGraph)
                    {
                        IMediaEvent fg = (IMediaEvent)this.AudioRenderPin.Filter.Graph;
                        if (lastSample != null)
                            ((FilterGraph)fg).SyncSource.AdviseTime(lastSample.EndTime, s => fg.NotifyEvent(new MediaEventArgs(this.AudioRenderPin.Filter, MediaNotification.Completed)), null);
                    }
                }

                if (sample.MediaType.MajorType == MediaMajorTypes.Audio)
                {
                    streamSample = new MediaStreamSample(this.audioStreamDescription, stream, 0, sample.Length, sample.StartTime.Ticks, emptyDict);
                }
                else if (sample.MediaType.MajorType == MediaMajorTypes.Video)
                {
                    streamSample = new MediaStreamSample(this.videoStreamDescription, stream, 0, sample.Length, sample.StartTime.Ticks, emptyDict);
                }

                lastSample = sample;

                this.ReportGetSampleCompleted(streamSample);
            }
            finally
            {
                if (sample != null)
                    sample.Release();
            }
        }

        protected override void OpenMediaAsync()
        {
            // MediaElemenent does not control graph
            lock (this)
            {
                stopped = false;
                mediaOpened = false;
            }
        }

        protected internal void ReportOpenMediaCompleted()
        {
            Dictionary<MediaSourceAttributesKeys, String> mediaSourceAttributes = new Dictionary<MediaSourceAttributesKeys, string>();

            // TODO:
            bool canSeek = false;
            TimeSpan duration;
            duration = TimeSpan.Zero;

            mediaSourceAttributes[MediaSourceAttributesKeys.Duration] = duration.Ticks.ToString(CultureInfo.InvariantCulture);
            mediaSourceAttributes[MediaSourceAttributesKeys.CanSeek] = canSeek.ToString();

            List<MediaStreamDescription> streams = new List<MediaStreamDescription>();

            if (AudioRenderPin.ConnectedMediaType != null)
            {
                Dictionary<MediaStreamAttributeKeys, String> mediaStreamAttributes = new Dictionary<MediaStreamAttributeKeys, string>();
                mediaStreamAttributes[MediaStreamAttributeKeys.CodecPrivateData] = AudioRenderPin.ConnectedMediaType.Format.ToCodecDataString();

                audioStreamDescription = new MediaStreamDescription(MediaStreamType.Audio, mediaStreamAttributes);
                streams.Add(audioStreamDescription);
            }
            if (VideoRenderPin.ConnectedMediaType != null)
            {
                Dictionary<MediaStreamAttributeKeys, String> mediaStreamAttributes = new Dictionary<MediaStreamAttributeKeys, string>();
                BitmapInfoHeader bf = this.VideoRenderPin.ConnectedMediaType.Format as BitmapInfoHeader;
                if (bf == null)
                    throw new NotSupportedException("Unknown video format");

                mediaStreamAttributes[MediaStreamAttributeKeys.Width] = bf.Width.ToString();
                mediaStreamAttributes[MediaStreamAttributeKeys.Height] = bf.Height.ToString();
                mediaStreamAttributes[MediaStreamAttributeKeys.VideoFourCC] = VideoRenderPin.ConnectedMediaType.FourCC;

                videoStreamDescription = new MediaStreamDescription(MediaStreamType.Video, mediaStreamAttributes);
                streams.Add(videoStreamDescription);
            }

            ReportOpenMediaCompleted(mediaSourceAttributes, streams.ToArray());
        }

        protected override void SeekAsync(long seekToTime)
        {
            CheckFilter();

            // MediaElement can't seek graph
            IMediaPosition position = this.AudioRenderPin.Filter.Graph as IMediaPosition;
            if (position == null)
                throw new InvalidOperationException("Cannot seek. Graph does not support it");

            ReportSeekCompleted(position.CurrentPosition.Ticks);
        }

        protected override void SwitchMediaStreamAsync(MediaStreamDescription mediaStreamDescription)
        {
            throw new NotImplementedException();
        }

        internal void ReceiveMediaSample(IMediaSample sample)
        {
            lock (samples)
            {

                if (this._audioRenderPin.Filter.State == FilterState.Stopped) return;

                //Debug.WriteLine("ReceiveMediaSample {0}-{1}", sample.StartTime, sample.EndTime);
                sample.AddRef();

                // Push data
                samples.Enqueue(sample);

                if (!mediaOpened)
                {
                    mediaOpened = true;
                    ReportOpenMediaCompleted();
                }

                // Call MediaElement
                sampleEvent.Set();
            }
        }

        internal void Stop()
        {
            lock (this)
            {
                // Tell to the wainting thread to exit
                stopped = true;
            }
            lock (samples)
            {
                // Release reamined samples
                foreach (IMediaSample s in samples)
                    s.Release();
                samples.Clear();

                sampleEvent.Set();
            }
        }

        internal void EndOfStream()
        {
            if (this.AudioRenderPin.ConnectedPin != null)
                ReceiveMediaSample(new EndStreamMedia() { MediaType = AudioRenderPin.ConnectedMediaType });
            if (this.VideoRenderPin.ConnectedPin != null)
                ReceiveMediaSample(new EndStreamMedia() { MediaType = VideoRenderPin.ConnectedMediaType });
        }

        private IMediaControl GetMediaControl()
        {
            IMediaControl control = this.AudioRenderPin.Filter.Graph as IMediaControl;
            if (control == null)
                throw new InvalidOperationException("Cannot control the graph. Graph does not support it");
            return control;
        }

        private void CheckFilter()
        {
            if (this.AudioRenderPin.Filter.Graph == null)
                throw new InvalidOperationException("Filter does not belong to any graph");
        }

        protected class EndStreamMedia : IMediaSample
        {
            #region IMediaSample Members

            byte[] IMediaSample.Data
            {
                get { return null; }
            }


            public AMMediaType MediaType
            {
                get;
                set;
            }

            #endregion

            #region IMediaSample Members


            long IMediaSample.Length
            {
                get
                {
                    return 0;
                }
                set
                {
                    throw new NotImplementedException();
                }
            }

            long IMediaSample.StartMediaTime
            {
                get
                {
                    throw new NotImplementedException();
                }
                set
                {
                    throw new NotImplementedException();
                }
            }

            long IMediaSample.EndMediaTime
            {
                get
                {
                    throw new NotImplementedException();
                }
                set
                {
                    throw new NotImplementedException();
                }
            }

            TimeSpan IMediaSample.StartTime
            {
                get
                {
                    return TimeSpan.Zero;
                }
                set
                {
                    throw new NotImplementedException();
                }
            }

            TimeSpan IMediaSample.EndTime
            {
                get
                {
                    return TimeSpan.Zero;
                }
                set
                {
                    throw new NotImplementedException();
                }
            }

            #endregion

            #region IUnknown Members

            void IUnknown.AddRef()
            {

            }

            void IUnknown.Release()
            {

            }

            #endregion
        }
    }
}
